// ### 主要包括
// 表单验证集合
// 深度载入函数
// 手机验证码处理
// canvas验证码绘制

const {
    $_toClass,
    $_isType,
    $_trimAll,
    $_hasOwn
} = require("./common");

/**
 * @description: 仿照微信小程序setData函数(具备其所有特性) ， 并在其之上做了一些扩展。
 * @param {Event}  event  // DOM Event 对象
 * @param {Object} keyObj //自定义密钥配置对象
 * @param {Object} modelConfig  [deep // 针对vue数组对象是否进行深度相应 
 *                     , module // 模块路径会拼接在每个载入路径前面 
 *                     , callback // 回调函数，第一个参数为所有载入的集合对象]
 * @return {void}
 */


function $_setData(event, keyObj, modelConfig) {
    const page = this;
    let AsskeyObj;
    // 进行参数合并  两者选其一   
    // event优先  
    if (event) {
        //DOMStringMap  https://developer.mozilla.org/zh-CN/docs/Web/API/DOMStringMap
        //兼容性： ie >= 11 其他浏览器在低版本都已兼容
        const eventData = event.currentTarget.dataset;
        const edModel = eventData.model;
        if (edModel) {
            AsskeyObj = {};
            let modelVal;
            // 在 vue  v-model指令存在的情况下，此方法只能弥补特定情况。
            if (!$_hasOwn(eventData, "value", false)) {
                //CustomEvent.detail   https://developer.mozilla.org/zh-CN/docs/Web/API/CustomEvent/detail
                modelVal = event.detail.value;
            } else {
                // 判断 data-value  属性是否存在
                // 当  ( data-value ="" ) 时也是需要载入
                modelVal = eventData.value;
            }
            AsskeyObj[edModel] = modelVal;
        } else {
            throw new Error("the event model is must be required");
        }
    } else {
        if (keyObj && $_toClass(keyObj) == "Object") {
            AsskeyObj = keyObj;
        } else {
            throw new Error("the keyObj is must be required and the type is Object");
        }
    }

    if (AsskeyObj) {
        // 保存需要写入的所有对象  
        // 减少 setData 合并额外没有变动属性  
        const modelAss = {};
        const AsskeyObj_keys = Object.keys(AsskeyObj);
        // 获取载入配置参数
        const {
            deep,
            module,
            callback
        } = Object.assign({
            deep: true
        }, modelConfig);
        if (AsskeyObj_keys.length) {
            AsskeyObj_keys.forEach(keyNode => {
                // 获取value
                const value = AsskeyObj[keyNode];
                // 为自定义新值提供可配置函数 arguments[0]为旧值
                const valueIsFun = typeof value === "function";

                let modelPath = keyNode;
                // 判断模块是否存在 为写入路径拼接上模块路径 
                if (module && typeof module === "string") {
                    modelPath = module + "." + modelPath;
                }
                // 如果存在分割符  , 或者 | 转换为数组
                // 消除空格 
                const modelArr = $_trimAll(modelPath, true).split(/[,|]/);
                // 对分割后的model进行循环写入
                modelArr.forEach(modelNode => {
                    // 转换modelNode上的数组元素为统一分隔符
                    const modelAga = modelNode.replace(/(\[)|(\])/gm, function (match, p1, p2) {
                        if (p1) {
                            return ".";
                        } else if (p2) {
                            return "";
                        } else {
                            return match;
                        }
                    });
                    //进行深度写入
                    const modelLeve = modelAga.split(".");
                    let copyData = page;
                    for (let j = 0, l = modelLeve.length; j < l; j++) {
                        const iterKey = modelLeve[j];
                        if (iterKey) {
                            const iterObj = copyData[iterKey];
                            if (j == l - 1) {
                                // 如果value为一个函数  则把 之前的value 作为函数的第一个值
                                // 函数 this  指向调用 page对象
                                const finalModelVal = copyData[iterKey];

                                // 是否对vue数组对象类型进行深度响应  Array.prototype.splice
                                const deepUse = deep && ($_toClass(copyData) === "Array");
                                if (deepUse) {
                                    if (!valueIsFun) {
                                        copyData.splice(iterKey, 1, value);
                                    } else {
                                        copyData.splice(iterKey, 1, value.call(page, finalModelVal));
                                    }
                                } else {
                                    if (!valueIsFun) {
                                        copyData[iterKey] = value;
                                    } else {
                                        copyData[iterKey] = value.call(page, finalModelVal);
                                    }
                                }
                                break;
                            } else {
                                copyData = iterObj;
                            }
                        } else {
                            throw new Error("iter leve is not be Null");
                        }
                    }
                    const copyKey = modelLeve[0];
                    modelAss[copyKey] = page[copyKey];

                });
            });
        } else {
            console.warn("AsskeyObj keys length is 0");
        }
        if (callback && typeof callback === "function") {
            callback(modelAss);
        }
    } else {
        throw new Error("missing necessary params");
    }
}
/**
 * @description: vuex载入函数
 * @param {Object} state // vuex state
 * @param {Object} modelConfig = { modelInfo:Object , modulePath:String|Function|Object }
 * @return {void} 
 */

function $_commit_model(state, commitConfig) {
    if (commitConfig && $_isType(commitConfig, "Object")) {
        const {
            modelInfo,
            modelConfig
        } = commitConfig;
        if (modelInfo && $_isType(modelInfo, "Object")) {
            const setConfigDefault = {
                deep: true
            };
            if (modelConfig) {
                const modelConfigType = $_toClass(modelConfig);
                if (modelConfigType === "String") {
                    setConfigDefault.module = modelConfig;
                } else if (modelConfigType === "Function") {
                    setConfigDefault.callback = modelConfig;
                } else if (modelConfigType === "Object") {
                    Object.assign(setConfigDefault, modelConfig);
                } else {
                    throw Error("param commitConfig.modulePath  type must be String or Object");
                }
            }
            $_setData.call(state, null, modelInfo, setConfigDefault);
        } else {
            throw Error("param commitConfig.modelInfo is required or type must be Object");
        }
    } else {
        throw Error("param commitConfig is required or type must be Object");
    }
}

/**
 * @description: 数字格式化
 * @param {String|Number}
 * @return {String}
 */

function $_checkTime(idx) {
    return ("0" + idx).slice(-2);
}

/**
 * @description: 
 * @param {String|Number|Date} dateParams // 日期对象  时间戳(数字 , 字符串)  
 * @param {String} formatKey // DD hh mm ss 正则匹配
 * @return {Object|null} // null  时间差已过
 */
function $_leftDate(dateParams, formatKey) {
    if (dateParams !== undefined) {
        const paramsType = typeof dateParams;
        let endDate = null;
        if (paramsType === "string" ||
            paramsType === "number"
        ) {
            endDate = new Date(dateParams);
        } else if (paramsType === "object") {
            endDate = dateParams;
        } else {
            throw TypeError("param dateParams type must be [String,Number,Date]");
        }
        const leftTimes = endDate.getTime() - new Date().getTime();
        if (leftTimes >= 0) {
            const leftDays = $_checkTime(parseInt(leftTimes / 1000 / 60 / 60 / 24));
            const leftHours = $_checkTime(parseInt(leftTimes / 1000 / 60 / 60 % 24));
            const leftMinutes = $_checkTime(parseInt(leftTimes / 1000 / 60 % 60));
            const leftSeconds = $_checkTime(parseInt(leftTimes / 1000 % 60));
            // 格式化字符串
            let formatVal;
            if (formatKey && typeof formatKey === "string") {
                formatVal = formatKey
                    .replace(/(DD)|(hh)|(mm)|(ss)/gm, function (match, DD, hh, mm, ss) {
                         if (DD) {
                            return leftDays;
                        } else if (hh) {
                            return leftHours;
                        } else if (mm) {
                            return leftMinutes;
                        } else if (ss) {
                            return leftSeconds;
                        }
                    });
            } else {
                throw Error("param formatKey is required or type must be String");
            }
            return {
                days: leftDays,
                hours: leftHours,
                minutes: leftMinutes,
                seconds: leftSeconds,
                format: formatVal
            }
        } else {
            return null;
        }
    } else {
        throw new ReferenceError("param dateParams must be required");
    }
}

/**
 * @description: 
 * @param {String|Number|Date} dateParams // 日期对象  时间戳(数字 , 字符串)  
 * @param {String} formatKey // 格式化文本  YYYY MM DD hh mm ss 正则匹配
 * @return {Object}
 */
function $_formatDate(dateParams, formatKey = "YYYY-MM-DD hh:mm:ss") {
    const dateType = $_toClass(dateParams);

    let dateObj;
    if (dateType === "Number" ||
        dateType === "String"
    ) {
        dateObj = new Date(dateParams);
    } else if (dateType === "Date") {
        dateObj = dateParams;
    } else {
        throw TypeError("param dateParams type must be [String,Number,Date]");
    }
    const year = dateObj.getFullYear();
    const month = $_checkTime(dateObj.getMonth() + 1)
    const date = $_checkTime(dateObj.getDate());
    const hours = $_checkTime(dateObj.getHours());
    const minutes = $_checkTime(dateObj.getMinutes());
    const seconds = $_checkTime(dateObj.getSeconds());
    let formatVal;
    // 格式化字符串
    if (formatKey && typeof formatKey === "string") {
        formatVal = formatKey
            .replace(/(YYYY)|(MM)|(DD)|(hh)|(mm)|(ss)/gm, function (match, YYYY, MM, DD, hh, mm, ss) {
                if (YYYY) {
                    return year;
                } else if (MM) {
                    return month;
                } else if (DD) {
                    return date;
                } else if (hh) {
                    return hours;
                } else if (mm) {
                    return minutes;
                } else if (ss) {
                    return seconds;
                }
            });
    } else {
        throw Error("param formatKey is required or type must be String");
    }
    return {
        year,
        month,
        date,
        hours,
        minutes,
        seconds,
        format: formatVal,
        timestamp: dateObj.getTime()
    };
}


module.exports = {
    $_setData,
    $_commit_model,
    $_checkTime,
    $_formatDate,
    $_leftDate,
   
}